#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


#include <windows.h>

extern int call_data_start();
extern int call_data_end();
static void* _luacall(void*d,int ref,void**ap);
void* luacall = &_luacall;
extern int call_data_lua_ref();
extern int call_data_lua_state();

static void* _luacall(void*d,int ref,void**ap){
    //printf("call %p %d %d\n",d,ref,ap[0]);
    lua_State*L = d;
    lua_rawgeti(L,LUA_REGISTRYINDEX,ref);
    if(lua_isfunction(L,-1)){
        lua_pushlightuserdata(L,ap);
        lua_call(L,1,LUA_MULTRET);
        void* ret = 0;
        if(lua_isinteger(L,-1))
            ret = (void*)((int)lua_tointeger(L,-1));
        else if(lua_islightuserdata(L,-1))
            ret = (void*)lua_touserdata(L,-1);
        else if(lua_isuserdata(L,-1))
            ret = *(void**)lua_touserdata(L,-1);
        else if(lua_isstring(L,-1))
            ret = (void*)lua_tostring(L,-1);
        return ret;
    }
    return 0;
}

static void* getcall(void*d,int rf){
    int len = &call_data_end - &call_data_start;
    len += sizeof(void*)*4;
    len &= ~3;
    asm(
    "  jmp _call_data_end\n"
    "  .globl _call_data_start\n"
    "  .globl _call_data_end\n"
    "_call_data_start:\n"
    "movl   %esp,%eax           \n "
    "pushl	%ebp                  \n" 
	"movl	%esp, %ebp            \n"
	"subl	$12, %esp             \n"
    "addl   $4,%eax               \n"
    "movl   %eax,8(%esp)\n"
    "_call_data_lua_call:\n"
	"movl	_luacall, %eax        \n"
	"movl	%eax, %ecx            \n"
    "_call_data_lua_ref:            \n"
	"movl	$0, %eax                 \n"
    "movl	%eax, 4(%esp)         \n"
    "_call_data_lua_state:          \n"
	"movl	$0, %eax                 \n"
	"movl	%eax, (%esp)          \n"
	"call	*%ecx                 \n"
	"nop                          \n"
	"leave                        \n"
    "ret\n"
    ".align 8\n"
    "_call_data_end:\n"
    );
    void*f = calloc(1,len);
    memcpy(f,call_data_start,len);
    
    int ref_offset = &call_data_lua_ref - &call_data_start;
    int data_offset = &call_data_lua_state - &call_data_start;
    
    void**ref = (f + ref_offset + 1);
    void**data = (f+ data_offset + 1);
    fflush(stdout);
     ref[0] = (void*)rf;
     data[0] = d;
    DWORD save;
    VirtualProtect(f,len,0x40 ,&save);
    return f;
}

static int lcall(lua_State*L){
    lua_pushvalue(L,1);
    int func_ref = luaL_ref(L,LUA_REGISTRYINDEX);
    lua_pushlightuserdata(L,getcall(L,func_ref));
    return 1;
}

int luaopen_callbackfunc(lua_State*L){
    lua_register(L,"lcall",lcall);
    return 0;
}

